#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import re
import shlex
import subprocess
import time
from typing import Iterator

from saporacle.comm.common_util import logger_watched, run_subprocess, remove_anypath
from saporacle.comm.constants import SAPOracleErrorCode, SAPOracleConsts
from saporacle.comm.logger import logger
from saporacle.comm.saporacle_exception import SAPOracleException
from saporacle.resource.saporacle import SAPOracle


class RestoreSAPOracle(SAPOracle):

    """SAP on Oracle 恢复能力"""

    def __init__(self, oracle_sid: str, sapsid: str, source_oracle_sid: str, source_sapsid: str, db_user: str = "",
                 db_pwd: str = "") -> None:
        logger.info(
            f"RestoreSAPOracle.__init__({oracle_sid}, {sapsid}, {source_oracle_sid}, {source_sapsid}, {db_user})")
        super().__init__(oracle_sid, sapsid, source_oracle_sid, source_sapsid, db_user, db_pwd)

    """brrecover 恢复"""

    def remove_temp_data_file(self) -> None:
        """
        :raise SAPOracleException:
        """
        temp_data_file = os.path.join(os.environ.get("SAPDATA1") or os.path.join(self.sapdata_home(), "sapdata1"),
                                      "temp_1", "temp.data1")
        remove_anypath(temp_data_file)

    def restore_data_path(self) -> str:
        name: str = SAPOracleConsts.RESTORE_TMP_DATAPATH.format(oracle_sid=self._source_oracle_sid)
        return os.path.join(SAPOracleConsts.TMP_PATH, name)

    def remove_restore_data_path(self) -> None:
        """
        :raise SAPOracleException:
        """
        remove_anypath(self.restore_data_path())

    def check_brrestore(self) -> None:
        """
        :raise SAPOracleException:
        """
        pass

    @logger_watched(3)
    def run_brrecover(self, detail_log: str, pit: str = None) -> Iterator[tuple[float, float]]:
        """
        :raise SAPOracleException:
        """
        log_name = os.path.basename(detail_log)
        tool = self._brtools_location() / "brrecover.exe"
        command = \
            (f"{tool} -b {log_name} -c force -d util_file -l E -f "
             f"{self._oracle_sid} -t reset -o dist,time -u") if not pit else \
                (f"{tool} -b {log_name} -c force -d util_file -l E -f "
                 f"{self._oracle_sid} -t dbpit -pit {pit} -o dist,time -u")
        logger.info(command)
        try:
            with self._brtools_outfile().open("w", encoding="utf-8") as f:
                process = subprocess.Popen(shlex.split(command, posix=os.name == "posix"),
                                           stdin=subprocess.PIPE, stdout=f, stderr=subprocess.STDOUT, text=True)
        except Exception as e:
            logger.error(f"Exception occurred while creating brrecover process. {command} {e}", exc_info=1)
            raise SAPOracleException(SAPOracleErrorCode.SUBPROCESS_EXCEPTION, command, "UnknownException")
        process.stdin.write(f"{self._db_user}/{self._db_pwd}\n") and process.stdin.flush()
        while True:
            yield self._brrecover_progress()
            if process.poll() is not None:
                break
            time.sleep(30)
        cont = self._brtools_outfile().read_text(encoding="utf-8")
        if "BRRECOVER completed successfully" not in cont:
            logger.error("BRRECOVER not completed successfully.")
            raise SAPOracleException(SAPOracleErrorCode.SUBPROCESS_EXCEPTION, command, "UnknownOutput")
        logger.info("BRRECOVER completed successfully.")

    def terminate_brrestore(self) -> None:
        """
        :raise SAPOracleException:
        """
        tool = self._brtools_location() / "brrestore.exe"
        command = f"{tool} -c force -g term"
        process = run_subprocess(command)
        if "completed successfully" not in process.stdout:
            logger.error(f"Brrestore terminated Failed. {process.stdout}")
            raise SAPOracleException(SAPOracleErrorCode.SUBPROCESS_EXCEPTION, command, "UnknownOutput")
        logger.info("Brrestore terminated successfully.")

    """private"""

    def _brrecover_progress(self) -> tuple[float, float]:
        """
        :raise SAPOracleException:
        """
        file = self._brtools_outfile()
        if not file.exists():
            logger.error(f"Brrecover outfile not exists. {file.absolute()}")
            raise SAPOracleException(SAPOracleErrorCode.UNKNOWN_ERROR)
        cont = file.read_text(encoding="utf-8")
        matches = re.findall(r"BR\d+I.+found to restore.+size ([\d.]+) MB", cont)
        if not matches:
            logger.warn(f"Cannot match brrecover total size.")
        total_size = sum((float(m) for m in matches), 0.0)
        matches = re.findall(r"#RESTORED.+#(\d+)", cont)
        if not matches:
            logger.warn(f"Cannot match brrecover restored size.")
        trans_size = sum((int(m) / 1024 / 1024 for m in matches), 0.0)
        return trans_size, total_size
